﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
using System.Threading;
using System.IO;
using System.Net;
namespace WindowsGame1
{
	public class Arena
	{
		public ArenaClient arenaClient;
		//static ReaderWriterLock rwl = new ReaderWriterLock(); //so that arenaClient's worker thread does not screw up our parsing of raw snapshot.
		public Tile[,] Walls;
		public Tile[,] Floors;
		private Thread arenaListener;
		public int Size;
		public int Team1Lifes
		{
			get
			{
				var lifes = 0;
				foreach (var p in Team1)
				{
					lifes += p.Lifes;
				}
				return lifes;
			}
		}
		public int Team2Lifes
		{
			get
			{
				var lifes = 0;
				foreach (var p in Team2)
				{
					lifes += p.Lifes;
				}
				return lifes;
			}
		}
		public List<Player> Team1;
		public List<Player> Team2;
		private float[] _timers;
		public Player Me 
		{
			get
			{
				return Team1[0];
			}
		}
		public Item[] Items;
		private World _world;
		public List<Bullet> ListOfMyBullets;
		public List<Bullet> ListOfRemoteBullets;
		public Light light;

		public Arena(World world, Tile[,] walls, Tile[,] floors, Item[] items, int size)
		{
			Walls = walls;
			Floors = floors;
			Size = size;
			Items = items;
			_world = world;
			ListOfMyBullets = new List<Bullet>();
			ListOfRemoteBullets = new List<Bullet>();
			Random rnd = new Random();
			int id = rnd.Next(0, 1000);
			Player me = new Player(_world, new Vector2(100, 100), 0, true);
			me.id = id;

			Team1 = new List<Player>();
			Team2 = new List<Player>();
			Team1.Add(me);
			Team2.Add(new Player(_world, new Vector2(140, 140), 2, false));
			Me.BulletEvent += AddBullet;
			_timers = new float[items.Length];

			light = new Light(Me.Position);

			#region networking

			//RecreateArenaClient("127.0.0.1");
			

			#endregion
		}

		public void Update(GameTime gameTime, Vector2 mousePos, int leftClick, int rightClick, int[] keysPressed)
		{
			if (this.arenaClient != null)
				DispatchArenaSnapshot();
			var x = (keysPressed[2] > 0 ? -1 : 0) + (keysPressed[3] > 0 ? 1 : 0);
			var y = (keysPressed[0] > 0 ? -1 : 0) + (keysPressed[1] > 0 ? 1 : 0);
			Me.Update(new Vector2(x, y), mousePos, leftClick, gameTime);
			// TEST
			if (keysPressed[4] > 0)
			{
				if (Me.CurrentHealth > 0)
				{
					Me.CurrentHealth--;
				}
			}
			foreach (var p in Team1)
			{
				if (p != Me)
				{
					p.Update(p.Position, p.Rotation, gameTime);
				}
				if (p.WaitingForRespawn && p == Me)
				{
					p.Position = GetNewSpawnSpot(Team1);
					p.WaitingForRespawn = false;
					p.SemiDead = false;
					p.CurrentDeathPenalty = 0;
				}
			}
			foreach (var p in Team2)
			{
				p.Update(p.Position, p.Rotation, gameTime);
			}

			//MAIN LOOP
			//CAUTION, BE GENTLE WITH THE CODE BELOW

			UpdateBullet();
			UpdateBullet(ListOfRemoteBullets);
			UpdateListOfWalls();
			foreach (var i in Items)
			{
				i.Update(gameTime);
				if (!Me.SemiDead && !Me.Dead && !i.PickedUp && Me.AmmoCount < Config.MaxAmmo && Me.BBox.Intersects(new Rectangle((int)i.Position.X + Config.TileSize / 2, (int)i.Position.Y + Config.TileSize / 2, i.BBox.Width, i.BBox.Height)))
				{
					Me.AmmoCount = (Me.AmmoCount + Config.AdditionalAmmo > Config.MaxAmmo) ? Config.MaxAmmo : Me.AmmoCount + Config.AdditionalAmmo;
					i.PickedUp = true;
					i.RespawnTime = Config.AmmoRespawnTime;
				}
			}
			_world.Step(1);
			//ListOfRemoteBullets.Clear();
			if (this.arenaClient != null)
				arenaClient.SendMyState(); 
			light.Update(Me.Position, gameTime);
			//END MAIN LOOP
		}

		private void UpdateListOfWalls()
		{
			var min = new Point((int)(Me.Position.X - (TVSGame.Resolution.X * 0.6f)), (int)(Me.Position.Y - (TVSGame.Resolution.Y * 0.6f)));
			var max = new Point((int)(Me.Position.X + (TVSGame.Resolution.X * 0.6f)), (int)(Me.Position.Y + (TVSGame.Resolution.Y * 0.6f)));
			var indices = new List<int>();
			for (var y = 0; y < Size; y++)
			{
			   for (var x = 0; x < Size; x++)
			   {
				   if(null != Walls[x,y])
				   {
					   Walls[x, y].Update(min, max);     //check for null value
				   }                 
			   }
			}
		}

		#region networking

		//TODO: design  raw byte array that holds all serializable information
		public void DispatchArenaSnapshot()
		{
			//Get most recent snapshot of whole Arena
			byte[] snapshot = arenaClient.receive_buffer;
 
			Stream stream = new MemoryStream(snapshot);
			BinaryReader reader = new BinaryReader(stream);
			int snapshotLength = snapshot.Length;
			if (snapshot[2] != 0 || snapshot[3] != 0)
			{
				for (int i = 0; i < 4; i++)
				{
					//Player.Id
					byte[] bytes = reader.ReadBytes(4);
					Array.Reverse(bytes);
					//get the int from the byte array
					int id = BitConverter.ToInt32(bytes, 0);
					//Player.X
					bytes = reader.ReadBytes(4);
					Array.Reverse(bytes);
					float x = BitConverter.ToSingle(bytes, 0);

					//Player.Y
					bytes = reader.ReadBytes(4);
					Array.Reverse(bytes);
					float y = BitConverter.ToSingle(bytes, 0);

					//Player.Rotation
					bytes = reader.ReadBytes(4);
					Array.Reverse(bytes);
					float rotation = BitConverter.ToSingle(bytes, 0);


					Vector2 newPos = new Vector2(x, y);


					Player player = null;
					foreach (Player p in Team2)
					{
						if (p.id == id)
						{
							player = p;
							p.Position = newPos;
							p.Rotation = rotation;
							break;
						}
					}

					//Add other player to local FFAPArticipants
					if (player == null && id != 0 && Me.id != id)
					{
						player = new Player(_world, new Vector2(x, y), 0, true);
						player.id = id;
						Team2.Add(player);
					}
					//Get enemy bullets
				   
					for (int b = 0; b < 16; b++)
					{
						bytes = reader.ReadBytes(4);
						Array.Reverse(bytes);
						float bX = BitConverter.ToSingle(bytes, 0);
						bytes = reader.ReadBytes(4);
						Array.Reverse(bytes);
						float bY = BitConverter.ToSingle(bytes, 0);
						bytes = reader.ReadBytes(4);
						Array.Reverse(bytes);
						float bRot = BitConverter.ToSingle(bytes, 0);

						if (bX != 0 && id != Me.id)
						{
							Bullet bul = new Bullet(new Vector2(bX, bY), bRot, Config.BasePlayerDamage, BulletType.Poison);// TODO: fix magic
							//ListOfRemoteBullets.Add(bul);
							AddRemoteBullet(bul);
						}

					}
				}
					
			}
			stream.Close();   
		}

		public byte[] GetMyStatePacket()
		{
			int id = Me.id;
			float myX, myY, myRotation;
			myX = this.Me.Position.X;
			myY = this.Me.Position.Y;
			myRotation = this.Me.Rotation;
			int bullet_count = ListOfMyBullets.Count;
			List<float> coordinates = new List<float>();
			coordinates.Add(myX);
			coordinates.Add(myY);
			coordinates.Add(myRotation);

			for (int i = 0; i < 16; i++)
			{
				if (i < bullet_count)
				{
					if (!ListOfMyBullets[i].isSent)
					{
						coordinates.Add(ListOfMyBullets[i].Position.X);
						coordinates.Add(ListOfMyBullets[i].Position.Y);
						coordinates.Add(ListOfMyBullets[i].Rotation);
						ListOfMyBullets[i].isSent = true;
					}
				}
				else
				{
					coordinates.Add(0);
					coordinates.Add(0);
					coordinates.Add(0);
				}

			}

			byte[] data = new byte[sizeof(int) + coordinates.Count * sizeof(float)];

			//Write id
			byte[] converted_int = BitConverter.GetBytes(id);
			if (BitConverter.IsLittleEndian)
			{
				Array.Reverse(converted_int);
			}
			for (int j = 0; j < sizeof(int); ++j)
			{
				data[j] = converted_int[j];
			}

			//Write floats
			for (int i = 0; i < coordinates.Count; ++i)
			{
				byte[] converted = BitConverter.GetBytes(coordinates[i]);
				if (BitConverter.IsLittleEndian)
					Array.Reverse(converted);
				for (int j = 0; j < sizeof(float); ++j)
				{
					data[sizeof(int) + i * sizeof(float) + j] = converted[j];
				}
			}



			return data;
		}

		#endregion


		/// <summary>
		/// add the shooting bullets into a list
		/// </summary>
		/// <param name="bullet">bullet that has already been created by trigging the event in Player</param>
		public void AddBullet(Bullet bullet)
		{
			light.CreateNewLightSource(bullet);
			ListOfMyBullets.Add(bullet);
			//arenaClient.SendMyState();
		}


		/// <summary>
		/// add the shooting bullets into a list
		/// </summary>
		/// <param name="bullet">bullet that has already been created by trigging the event in Player</param>
		public void AddRemoteBullet(Bullet bullet)
		{
			light.CreateNewLightSource(bullet);
			ListOfRemoteBullets.Add(bullet);
		}

		/// <summary>
		/// Update bullet current position as well as deleting the bullet from list if intersect with the walls or not
		/// </summary>
		public void UpdateBullet()
		{
		   if (ListOfMyBullets.Count > 0)
			{
				for (int i = ListOfMyBullets.Count - 1; i >= 0; i--)
				{
					var minY = ListOfMyBullets[i].Position.Y / Config.TileSize - 2;
					minY = (minY < 0) ? 0 : minY;
					var maxY= minY + 4;
					maxY = (maxY > Size) ? Size : maxY;

					var minX = ListOfMyBullets[i].Position.X / Config.TileSize - 2;
					minX = (minX < 0) ? 0 : minX;
					var maxX = minX + 4;
					maxX = (maxX > Size) ? Size : maxX;

					ListOfMyBullets[i].Update();
					for (var y = minY; y < maxY; y++)
					{
						for (var x = minX; x < maxX ; x++)
						{
							if (Walls[(int)x, (int)y] != null)
							{
								if (ListOfMyBullets.Count > 0 && i < ListOfMyBullets.Count)
								{
									if (ListOfMyBullets[i].BoundingBox.Intersects(Walls[(int)x, (int)y].BBox))
									{
										ListOfMyBullets[i].DisposeBullet();
										ListOfMyBullets.RemoveAt(i);
									}
								}
							}
						}
					}
				}
				for (int i = ListOfMyBullets.Count - 1; i >= 0; i--)
				{
					foreach (var p in Team2)
					{
						if (ListOfMyBullets.Count > 0 && i < ListOfMyBullets.Count)
						{
							if ( Vector2.Distance(ListOfMyBullets[i].Position, p.Position) < (p.GetSize().X / 2.2f + ListOfMyBullets[i].GetSize().X / 2.2f))//ListOfMyBullets[i].BoundingBox.Intersects(new Rectangle(p.BBox.X - (p.GetSize().X / 2), p.BBox.Y - (p.GetSize().Y / 2), p.GetSize().X, p.GetSize().Y)))
							{
								p.AddDamage(ListOfMyBullets[i].Damage);
								ListOfMyBullets[i].DisposeBullet();
								ListOfMyBullets.RemoveAt(i);
							}
						}
					}
					foreach (var p in Team1)
					{
						if (ListOfMyBullets.Count > 0 && i < ListOfMyBullets.Count)
						{
							if (p != Me && ListOfMyBullets[i].BoundingBox.Intersects(new Rectangle(p.BBox.X - (p.GetSize().X / 2), p.BBox.Y - (p.GetSize().Y / 2), p.GetSize().X, p.GetSize().Y)))
							{
								p.AddDamage(ListOfMyBullets[i].Damage);
								ListOfMyBullets[i].DisposeBullet();
								ListOfMyBullets.RemoveAt(i);
							}
						}
					}
				}
			}
		}

		public void UpdateBullet(List<Bullet> buls)
		{
			if (buls.Count > 0)
			{
				for (int i = buls.Count - 1; i >= 0; i--)
				{
					var minY = buls[i].Position.Y / Config.TileSize - 2;
					minY = (minY < 0) ? 0 : minY;
					var maxY = minY + 4;
					maxY = (maxY > Size) ? Size : maxY;

					var minX = buls[i].Position.X / Config.TileSize - 2;
					minX = (minX < 0) ? 0 : minX;
					var maxX = minX + 4;
					maxX = (maxX > Size) ? Size : maxX;

					buls[i].Update();
					for (var y = minY; y < maxY; y++)
					{
						for (var x = minX; x < maxX; x++)
						{
							if (Walls[(int)x, (int)y] != null)
							{
								if (buls.Count > 0 && i < buls.Count)
								{
									if (buls[i].BoundingBox.Intersects(Walls[(int)x, (int)y].BBox))
									{
										buls[i].DisposeBullet();
										buls.RemoveAt(i);
									}
								}
							}
						}
					}
				}
				//for (int i = buls.Count - 1; i >= 0; i--)
				//{
				//    foreach (var p in Team2)
				//    {
				//        if (buls.Count > 0 && i < buls.Count)
				//        {
				//            if (Vector2.Distance(buls[i].Position, p.Position) < (p.GetSize().X / 2.2f + buls[i].GetSize().X / 2.2f))//ListOfMyBullets[i].BoundingBox.Intersects(new Rectangle(p.BBox.X - (p.GetSize().X / 2), p.BBox.Y - (p.GetSize().Y / 2), p.GetSize().X, p.GetSize().Y)))
				//            {
				//                p.CurrentHealth -= buls[i].TotalDamage;
				//                buls[i].DisposeBullet();
				//                buls.RemoveAt(i);
				//            }
				//        }
				//    }
				//    foreach (var p in Team1)
				//    {
				//        if (buls.Count > 0 && i < buls.Count)
				//        {
				//            if (p != Me && buls[i].BoundingBox.Intersects(new Rectangle(p.BBox.X - (p.GetSize().X / 2), p.BBox.Y - (p.GetSize().Y / 2), p.GetSize().X, p.GetSize().Y)))
				//            {
				//                p.CurrentHealth -= buls[i].TotalDamage;
				//                buls[i].DisposeBullet();
				//                buls.RemoveAt(i);
				//            }
				//        }
				//    }
				//}
			}
		}

		private Vector2 GetNewSpawnSpot(List<Player> team)
		{
			var r = new Random();
			Vector2 returner = Vector2.Zero;
			var respawn = false;
			var indices = new List<int>();
			for (var i = 0; i < team.Count; i++)
			{
				if (!team[i].Dead && !team[i].SemiDead && team[i] != Me)
				{
					indices.Add(i);
				}
			}
			while (!respawn)
			{
				respawn = true;
				if (indices.Count > 0)
				{
					var index = indices[r.Next(indices.Count)];
					var position = team[index].Position;
					var offset = new Vector2(Config.TileSize * r.Next(Config.RespawnDistance), Config.TileSize * r.Next(Config.RespawnDistance));
					returner = (r.Next(2) < 1) ? position - offset : position + offset;
					returner.X -= returner.X % Config.TileSize;
					returner.Y -= returner.Y % Config.TileSize;
				}
				else
				{
					var min = Config.TileSize * 3;
					returner = new Vector2(r.Next(min, (Size * Config.TileSize) - min), r.Next(min, (Size * Config.TileSize) - min));
				}
				if (this.Walls[(int)returner.X / Config.TileSize, (int)returner.Y / Config.TileSize] != null)
				{
					respawn = false;
				}
			}
			return returner;
		}

		internal void RecreateArenaClient(string p)
		{
			if (this.arenaListener != null)
				this.arenaListener.Abort();
			this.arenaClient = new ArenaClient(this, p);
			this.arenaListener = new Thread(new ThreadStart(arenaClient.StartListener));
			this.arenaListener.IsBackground = true;
			this.arenaListener.Start();
		}
	}
}
